﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using AnthStat.Domain.Events;

namespace AnthStat.Domain
{
    /// <summary>
    /// Class representation of a person - parent, child, or adolescent that may be operated
    /// on by the software
    /// </summary>
    public sealed class Person
    {
        #region Events
        public event EventHandler DateBirthChanged;
        public event EventHandler<MeasurementEventArgs> MeasurementAdded;
        public event EventHandler<MeasurementEventArgs> MeasurementRemoved;
        #endregion // Events

        #region Members
        private readonly List<Measurement> _measurements = new List<Measurement>();
        private DateTime? _dateBirth;
        private bool _isDateBirthEstimated = false;
        private string _sexString = "F";
        private Sex _sex = Sex.Female;
        private readonly bool _isLimitedToOneMeasurement = false;
        private Project _project = null;
        #endregion // Members

        #region Constructors
        /// <summary>
        /// Constructor for a person
        /// </summary>
        /// <param name="id">The GUID for the person</param>
        /// <param name="dateCreated">The date the person record was originally created</param>
        /// <param name="numericId">The integer ID of the person</param>
        /// <param name="project">The project this person belongs to</param>
        /// <param name="limitToOneMeasurementOnly">Whether to limit this person to having only one measurement</param>
        public Person(string id, DateTime dateCreated, int numericId, Project project, bool limitToOneMeasurementOnly = false)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(id));
            Contract.Requires(id.Length == 36);

            // post
            Contract.Ensures(GlobalRecordId != null);

            GlobalRecordId = id;
            NumericId = numericId;
            DateCreated = dateCreated;
            DateUpdated = DateCreated;

            _project = project;
            _isLimitedToOneMeasurement = limitToOneMeasurementOnly;

            FirstName = String.Empty;
            LastName = String.Empty;
            StreetAddress = String.Empty;
            City = String.Empty;
            Adm1 = String.Empty;
            Adm2 = String.Empty;
            Adm3 = String.Empty;
            PostalCode = String.Empty;
            Country = String.Empty;
            HomePhoneNumber = String.Empty;
            WorkPhoneNumber = String.Empty;
            CellPhoneNumber = String.Empty;
            Email = String.Empty;
            Race = String.Empty;
            Ethnicity = String.Empty;

            Cluster = 0;
            Team = String.Empty;
            Household = String.Empty;

            UserCreated = String.Empty;
            UserUpdated = String.Empty;
        }
        #endregion // Constructors

        #region Properties

        public int NumericId { get; private set; }
        public string UserCreated { get; set; }
        public string UserUpdated { get; set; }
        public bool IsMeasureable { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Sex Sex
        {
            get
            {
                return _sex;
            }
            set
            {
                if (!_sex.Equals(value))
                {
                    _sex = value;

                    if (value.Equals(Sex.Female))
                    {
                        SexString = "F";
                    }
                    else if (value.Equals(Sex.Male))
                    {
                        SexString = "M";
                    }
                    else
                    {
                        throw new InvalidOperationException();
                    }

                    if (_measurements != null)
                    {
                        foreach (var m in _measurements)
                        {
                            m.RecalculateScores();
                        }
                    }
                }
            }
        }

        public string SexString
        {
            get
            {
                return _sexString;
            }
            internal set
            {
                if (!_sexString.Equals(value, StringComparison.OrdinalIgnoreCase))
                {
                    _sexString = value;

                    if (value.Equals("F", StringComparison.OrdinalIgnoreCase))
                    {
                        Sex = Sex.Female;
                    }
                    else if (value.Equals("M", StringComparison.OrdinalIgnoreCase))
                    {
                        Sex = Sex.Male;
                    }
                    else
                    {
                        throw new InvalidOperationException();
                    }
                }
            }
        }

        public DateTime? DateBirth
        {
            get
            {
                return _dateBirth;
            }
            set
            {
                if (_dateBirth != value)
                {
                    _dateBirth = value;
                    if (DateBirthChanged != null)
                    {
                        DateBirthChanged(this, new EventArgs());
                    }

                    if (_measurements != null)
                    {
                        foreach (Measurement m in _measurements)
                        {
                            m.RecalculateAge();
                            m.RecalculateScores();
                        }
                    }
                }
            }
        }
        public bool IsDateBirthEstimated
        {
            get
            {
                return _isDateBirthEstimated;
            }
            set
            {
                if (_isDateBirthEstimated != value)
                {
                    _isDateBirthEstimated = value;

                    if (_measurements != null)
                    {
                        foreach (Measurement m in _measurements)
                        {
                            m.RecalculateAge();
                            m.RecalculateScores();
                        }
                    }
                }
            }
        }

        public DateTime DateCreated { get; set; }
        public DateTime DateUpdated { get; set; }
        public string StreetAddress { get; set; }
        public string City { get; set; }
        public string Adm1 { get; set; }
        public string Adm2 { get; set; }
        public string Adm3 { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string HomePhoneNumber { get; set; }
        public string WorkPhoneNumber { get; set; }
        public string CellPhoneNumber { get; set; }
        public string Email { get; set; }
        public double? Latitude { get; set; }
        public double? Longitude { get; set; }
        public string Race { get; set; }
        public string Ethnicity { get; set; }
        public string GlobalRecordId { get; set; }
        public int Cluster { get; set; }
        public string Team { get; set; }
        public string Household { get; set; }

        public IEnumerable<Measurement> Measurements
        {
            get
            {
                return _measurements.AsEnumerable();
            }
        }

        #endregion // Properties

        #region Methods
        /// <summary>
        /// Gets the indicator set for the project
        /// </summary>
        /// <returns>IndicatorSet</returns>
        internal IndicatorSet GetIndicatorSet()
        {
            if (_project == null) return IndicatorSet.WHO20062007;
            return _project.GetIndicatorSet();
        }

        /// <summary>
        /// Assigns the project class this person belongs to
        /// </summary>
        /// <param name="project">The project to attach to this person</param>
        internal void AttachProject(Project project)
        {
            //// pre
            //Contract.Requires(_project == null);

            //// post
            //Contract.Ensures(_project != null);

            _project = project;
        }

        /// <summary>
        /// Adds a measurement for the person
        /// </summary>
        /// <param name="measurement">The measurement to add to this person</param>
        public void AddMeasurement(Measurement measurement)
        {
            // pre
            Contract.Requires(measurement != null);
            Contract.Requires(measurement.Person == this);
            Contract.Requires(!Measurements.Contains(measurement));

            // post
            Contract.Ensures(Measurements.Contains(measurement));

            // for nutritional surveys, only one measurement is taken per person
            if (_isLimitedToOneMeasurement && _measurements.Count > 1)
            {
                throw new InvalidOperationException("Cannot add more than one measurement to this person when _isLimitedToOneMeasurement is true.");
            }

            measurement.DateMeasurementUpdated += measurement_DateMeasurementUpdated;
            measurement.ScoresUpdated += measurement_ScoresUpdated;

            DateTime measurementDate = measurement.DateMeasurement;

            bool inserted = false;
            for (int i = 0; i < _measurements.Count; i++)
            {
                if (_measurements[i].DateMeasurement > measurementDate)
                {
                    _measurements.Insert(0, measurement);
                    inserted = true;
                    break;
                }
            }

            if (!inserted)
            {
                _measurements.Add(measurement);
            }

            if (MeasurementAdded != null)
            {
                MeasurementAdded(this, new MeasurementEventArgs(measurement));
            }
        }

        private void measurement_ScoresUpdated(object sender, EventArgs e)
        {
            // 
        }

        private void measurement_DateMeasurementUpdated(object sender, EventArgs e)
        {
            // 
        }

        /// <summary>
        /// Removes a measurement from this person
        /// </summary>
        /// <param name="measurement">The measurement to remove</param>
        public void RemoveMeasurement(Measurement measurement)
        {
            // pre
            Contract.Requires(measurement != null);
            Contract.Requires(measurement.Person == this);
            Contract.Requires(Measurements.Contains(measurement));

            // post
            Contract.Ensures(!Measurements.Contains(measurement));

            measurement.DateMeasurementUpdated -= measurement_DateMeasurementUpdated;
            measurement.ScoresUpdated -= measurement_ScoresUpdated;

            _measurements.Remove(measurement);

            if (MeasurementRemoved != null)
            {
                MeasurementRemoved(this, new MeasurementEventArgs(measurement));
            }
        }

        /// <summary>
        /// Removes a measurement from this person
        /// </summary>
        /// <param name="globalRecordId">The GlobalRecordId of the measurement to remove</param>
        public void RemoveMeasurement(Guid globalRecordId)
        {
            Contract.Requires(globalRecordId != null);

            string guid = globalRecordId.ToString();

            foreach (var measurement in _measurements)
            {
                if (measurement.GlobalRecordId.Equals(guid, StringComparison.OrdinalIgnoreCase))
                {
                    RemoveMeasurement(measurement);
                    break;
                }
            }
        }
        #endregion // Methods
    }
}
